#include "stdafx.h"
#include "SerialPortReader.h"
#include <memory>
#include <string>
#include <sstream>
#include <exception>

using std::unique_ptr;
using std::string;
using std::ostringstream;
using std::exception;

#include <iostream>

namespace Sensor {

	std::set<int> SerialPortReader::getPorts() {
		std::set<int> ports;
		HANDLE handle;

		for (int port=0; port<255; port++) {

			handle = CreateFile(
				getPortFilename(port).c_str(),	// port
				GENERIC_READ,	// open mode
				0,				// no sharing
				0,              // default security
				OPEN_EXISTING,  // action
				0,              // 
				0
				);

			if (handle != INVALID_HANDLE_VALUE) {
				ports.insert(port);

				if (!CloseHandle(handle)) {
					throw exception("Did not close serial port successfully.");
				}
			}
		}

		return ports;
	}

	SerialPortReader::SerialPortReader(unsigned int bufferSize) : handle(nullptr) {
		this->bufferSize = bufferSize;
	}

	std::string SerialPortReader::getPortFilename(int port) {
		char numStr[4];
		itoa(port, numStr, 10);

		std::string portFilename = "\\\\.\\COM";
		portFilename += numStr;
		return portFilename;
	}

	void SerialPortReader::setBufferSize(int bufferSize) {
	}

	void SerialPortReader::setBaudRate(int buadRate) {
	}

	/*
	port in range 0-255
	*/
	void SerialPortReader::open(int port) {	
		open(getPortFilename(port));
	}

	void SerialPortReader::open(std::string portName) {

		// Open the serial port
		handle = CreateFile(
			portName.c_str(),	// port
			GENERIC_READ,	// open mode
			0,				// no sharing
			0,              // default security
			OPEN_EXISTING,  // action
			0,              // 
			0
			);

		// Make sure the serial port was opened successfully
		if (handle == INVALID_HANDLE_VALUE) {
			ostringstream message;
			if (GetLastError() == ERROR_FILE_NOT_FOUND) {
				message << "Couldn't find the specified port: " << portName;
				throw exception(message.str().c_str());
			} else if(GetLastError() == ERROR_INVALID_NAME) {
				message << "Invalid port name: " << portName;
				throw exception(message.str().c_str());
			} else {
				message << "Error " << GetLastError() << " opening port: " << portName;
				throw exception(message.str().c_str());
			}
		}

		// Try to get the current configuration of the serial port
		if (!GetCommState(handle, &config)) {
			throw exception("Couldn't access serial port configuration.");
		}

		// Modify the serial port configuration
		config.BaudRate = CBR_9600;

		// Set the configuration of serial communication port
		if (!SetCommState(handle, &config)) {
			throw exception("Couldn't configure serial port.");
		}

		// Setup the timeout configuration
		COMMTIMEOUTS timeout;
		timeout.ReadIntervalTimeout = 3;
		timeout.ReadTotalTimeoutMultiplier = 3;
		timeout.ReadTotalTimeoutConstant = 2;
		timeout.WriteTotalTimeoutMultiplier = 3;
		timeout.WriteTotalTimeoutConstant = 2;

		// Set the timeout configuration
		if (!SetCommTimeouts(handle, &timeout)) {
			throw exception("Couldn't configure serial port timeout behavior.");
		}
	}

	SerialPortReader::~SerialPortReader() {
		// close(); FIXME: deallocate here?
	}

	string SerialPortReader::readLine() {
		unique_ptr<char[]> data(new char[bufferSize]);
		unsigned long bytesRead = 0;

		if (!ReadFile(handle, data.get(), bufferSize, &bytesRead, 0)) {
			throw exception("Serial port readline failed.");
		}

		if (bytesRead == 0) {
			return string();
		} else if (bytesRead < bufferSize) {
			data[bytesRead] = '\0';
			return string(data.release());
		} else {
			ostringstream message;
			message << "Buffer overflow: " << bytesRead << " > " << bufferSize;
			throw exception(message.str().c_str());
		}
	}


	void SerialPortReader::close() {
		if (!CloseHandle(handle)) {
			throw exception("Did not close serial port successfully.");
		}
	}

}